package br.senai.sc.controller.business;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.List;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.jboss.seam.international.status.Messages;
import org.jboss.seam.international.status.builder.BundleKey;

import br.senai.sc.model.BaciaHidrografica;
import br.senai.sc.model.Cidade;
import br.senai.sc.util.SystemConstants;

/**
 * Contém as regras de negócios relacionadas a entidade
 * {@link BaciaHidrografica}.
 * 
 * @author Tiago Wanke Marques &lt;tiago.marques@sc.senai.br&gt;
 */
public class BaciaHidrograficaBusiness implements Serializable {

	private static final long serialVersionUID = 1L;

	@PersistenceContext
	private EntityManager em;

	@Inject
	private Messages messages;

	@Inject
	private CidadeBusiness cidadeBusiness;

	/**
	 * @param bacia
	 * @return True caso a {@link BaciaHidrografica} exista, false caso
	 *         contrário
	 */
	public boolean existe(BaciaHidrografica bacia) {

		if ((bacia == null) || (bacia.getIdBaciaHidrografica() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica b WHERE id_bacia_hidrografica = :idBaciaHidrografica")
				.setParameter("idBaciaHidrografica",
						bacia.getIdBaciaHidrografica()).getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * Este método não considera se o id da {@link BaciaHidrografica} informada
	 * é o mesmo da encontrada no banco.
	 * 
	 * @param bacia
	 * @return True se existir uma {@link BaciaHidrografica} com mesmo nome
	 *         cadastrada no banco, false caso contrário.
	 */
	public boolean baciaComMesmoNome(BaciaHidrografica bacia) {

		if ((bacia == null) || (bacia.getNome() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica bh WHERE nome LIKE :nome")
				.setParameter("nome", bacia.getNome()).getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * @param
	 * @return True caso exista uma {@link BaciaHidrografica} com mesmo nome,
	 *         porém com id diferente, false caso contrário.
	 */
	public boolean baciaComMesmoNomeIdDiferente(BaciaHidrografica bacia) {

		if ((bacia == null) || (bacia.getNome() == null)
				|| (bacia.getIdBaciaHidrografica() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica bh WHERE nome LIKE :nome AND id_bacia_hidrografica != :idBaciaHidrografica")
				.setParameter("nome", bacia.getNome())
				.setParameter("idBaciaHidrografica",
						bacia.getIdBaciaHidrografica()).getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * @param baciaHidrografica
	 * @return True caso a {@link BaciaHidrografica} possui referência para
	 *         {@link br.senai.sc.model.CorpoHidrico}, false caso contrário.
	 */
	public boolean contemCorpoHidrico(BaciaHidrografica baciaHidrografica) {

		if ((baciaHidrografica == null)
				|| (baciaHidrografica.getIdBaciaHidrografica() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM corpo_hidrico ch WHERE id_bacia_hidrografica = :idBaciaHidrografica")
				.setParameter("idBaciaHidrografica",
						baciaHidrografica.getIdBaciaHidrografica())
				.getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * @param baciaHidrografica
	 * @param cidade
	 * @return True caso a {@link Cidade} informada esteja na
	 *         {@link BaciaHidrografica}, false caso contrário.
	 */
	public boolean possuiCidade(BaciaHidrografica baciaHidrografica,
			Cidade cidade) {

		if ((baciaHidrografica == null)
				|| (baciaHidrografica.getIdBaciaHidrografica() == null)
				|| (cidade == null) || (cidade.getIdCidade() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica_cidade bhc WHERE id_bacia_hidrografica = :idBaciaHidrografica AND id_cidade = :idCidade")
				.setParameter("idBaciaHidrografica",
						baciaHidrografica.getIdBaciaHidrografica())
				.setParameter("idCidade", cidade.getIdCidade())
				.getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * @return True caso exista {@link BaciaHidrografica} cadastrada no banco,
	 *         false caso contrário.
	 */
	public boolean existeBaciaHidrograficaCadastrada() {

		BigInteger total = (BigInteger) em.createNativeQuery(
				"SELECT count(*) FROM bacia_hidrografica bh").getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * @param cidade
	 * @return True caso a {@link Cidade} já esteja cadastrada em alguma
	 *         {@link BaciaHidrografica} false caso contrário.
	 */
	public boolean cidadeJaEstaEmBacia(Cidade cidade) {

		if ((cidade == null) || (cidade.getIdCidade() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica_cidade bhc WHERE id_cidade = :idCidade")
				.setParameter("idCidade", cidade.getIdCidade())
				.getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * 
	 * @param cidade
	 * @param baciaHidrografica
	 * @return True caso a {@link Cidade} informada esteja cadastrada em uma
	 *         {@link BaciaHidrografica} que seja diferente da informada, false
	 *         caso contrário.
	 */
	public boolean cidadeJaEstaEmOutraBacia(Cidade cidade,
			BaciaHidrografica baciaHidrografica) {

		if ((cidade == null) || (cidade.getIdCidade() == null)
				|| (baciaHidrografica == null)
				|| (baciaHidrografica.getIdBaciaHidrografica() == null)) {
			return false;
		}

		BigInteger total = (BigInteger) em
				.createNativeQuery(
						"SELECT count(*) FROM bacia_hidrografica_cidade bhc WHERE id_cidade = :idCidade AND id_bacia_hidrografica != :idBaciaHidrografica")
				.setParameter("idCidade", cidade.getIdCidade())
				.setParameter("idBaciaHidrografica",
						baciaHidrografica.getIdBaciaHidrografica())
				.getSingleResult();

		return total.intValue() > 0;
	}

	/**
	 * Executa os métodos necessários para verificar se a
	 * {@link BaciaHidrografica} pode ser salva. Caso houver alguma
	 * inconsistência adiciona ao atributo de mensagens da classe.
	 * 
	 * @param baciaHidrografica
	 * @return True caso a {@link BaciaHidrografica} pode ser salva, false caso
	 *         contrário.
	 */
	public boolean podeSalvar(BaciaHidrografica baciaHidrografica) {

		messages.clear();

		for (Cidade cidade : baciaHidrografica.getCidades()) {
			if (!cidadeBusiness.cidadeExiste(cidade)) {
				messages.error(new BundleKey(
						SystemConstants.PATH_TO_APPLICATION_MESSAGES,
						"cidades.validacao.cidadeInexistente"), cidade
						.getNome());
			} else { // se a cidade existir, verifica se ela já está associada a
						// uma Bacia Hidrográfica

				if (cidadeJaEstaEmBacia(cidade)) {
					messages.error(new BundleKey(
							SystemConstants.PATH_TO_APPLICATION_MESSAGES,
							"bacias.validacao.cidadeJaAssociadaABacia"), cidade
							.getNome());
				}
			}

		}

		if (baciaComMesmoNome(baciaHidrografica)) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_APPLICATION_MESSAGES,
					"bacias.validacao.baciaComMesmoNome"));
		}

		return messages.isEmpty();
	}

	/**
	 * Executa os métodos necessários para verificar se a
	 * {@link BaciaHidrografica} pode ser editada. Caso houver alguma
	 * inconsistência adiciona ao atributo de mensagens da classe.
	 * 
	 * @param baciaHidrografica
	 * @return True caso a {@link BaciaHidrografica} pode ser editada, false
	 *         caso contrário.
	 */
	public boolean podeEditarBaciaHidrografica(
			BaciaHidrografica baciaHidrografica) {

		messages.clear();

		if (!existe(baciaHidrografica)) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_APPLICATION_MESSAGES,
					"bacias.inexistente"));
			return false;
		}

		if (baciaComMesmoNomeIdDiferente(baciaHidrografica)) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_APPLICATION_MESSAGES,
					"bacias.validacao.baciaComMesmoNome"));
		}

		// verifica se alguma cidade informada não existe
		for (Cidade cidade : baciaHidrografica.getCidades()) {
			if (!cidadeBusiness.cidadeExiste(cidade)) {
				messages.error(new BundleKey(
						SystemConstants.PATH_TO_APPLICATION_MESSAGES,
						"cidades.validacao.cidadeInexistente"), cidade
						.getNome());
			} else { // se a cidade existir

				// verifica se ela já está associada a uma Bacia Hidrográfica
				if (cidadeJaEstaEmOutraBacia(cidade, baciaHidrografica)) {
					messages.error(new BundleKey(
							SystemConstants.PATH_TO_APPLICATION_MESSAGES,
							"bacias.validacao.cidadeJaAssociadaABacia"), cidade
							.getNome());
				}
			}
		}

		// verifica se alguma cidade removida está sendo utilizada por corpo
		// hidrico
		for (Cidade cidade : cidadesRemovidas(baciaHidrografica)) {

			if (cidadeBusiness.contemCorpoHidrico(cidade)) {
				messages.error(new BundleKey(
						SystemConstants.PATH_TO_APPLICATION_MESSAGES,
						"bacias.validacao.cidadeAssociadaACorpoHidrico"),
						cidade.getNome());
			}
		}

		return messages.isEmpty();
	}

	/**
	 * O método compara a lista de {@link Cidade} (a nível de objeto) da
	 * {@link BaciaHidrografica} com a lista de {@link Cidade} da mesma
	 * cadastradas no banco de dados. Dessa forma temos a lista de
	 * {@link Cidade} removidas da {@link BaciaHidrografica}, porém ainda não
	 * foram persistidas.
	 * 
	 * @param baciaHidrografica
	 * @return Lista de {@link Cidade} removidas da {@link BaciaHidrografica}.
	 */
	private List<Cidade> cidadesRemovidas(BaciaHidrografica baciaHidrografica) {

		List<Cidade> cidadesObjeto = baciaHidrografica.getCidades(); // pega a
																		// lista
																		// de
																		// cidades
																		// que
																		// existe
																		// no
																		// objeto
																		// informado
		BaciaHidrografica baciaHidrograficaCadastrada = em.find(
				BaciaHidrografica.class,
				baciaHidrografica.getIdBaciaHidrografica());
		List<Cidade> cidadesDoBanco = baciaHidrograficaCadastrada.getCidades(); // pega
																				// a
																				// lista
																				// de
																				// cidades
																				// cadastradas
																				// no
																				// banco
		cidadesDoBanco.removeAll(cidadesObjeto); // deixa apenas as que existe
													// no banco, porém não
													// existem no objeto, ou
													// seja, as que foram
													// removidas do objeto
		em.detach(baciaHidrograficaCadastrada); // retiro da transação do
												// hibernate para não ser
												// commitada essa alteração da
												// lista.
		return cidadesDoBanco;
	}

	/**
	 * Executa os métodos necessários para verificar se a
	 * {@link BaciaHidrografica} pode ser removida. Caso houver alguma
	 * inconsistência adiciona ao atributo de mensagens da classe.
	 * 
	 * @param baciaHidrografica
	 * @return True caso a {@link BaciaHidrografica} pode ser removida, false
	 *         caso contrário.
	 */
	public boolean podeRemover(BaciaHidrografica baciaHidrografica) {

		messages.clear();

		if (!existe(baciaHidrografica)) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_APPLICATION_MESSAGES,
					"bacias.inexistente"));
		}

		if (contemCorpoHidrico(baciaHidrografica)) {
			messages.error(new BundleKey(
					SystemConstants.PATH_TO_APPLICATION_MESSAGES,
					"bacias.validacao.contemCorpoHidrico"));
		}

		return messages.isEmpty();
	}
}
